package com.ian.common.utils;

import com.ian.common.constant.Constants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;
//import javax.crypto.SecretKey;
//import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.Map;

/**
 * 在 JWT 的实践中，引入 Refresh Token，将会话管理流程改进如下:
 *   客户端使用用户名密码进行认证
 *   服务端生成有效时间较短的 Access Token（例如 10 分钟），和有效时间较长的 Refresh Token（例如 7 天）
 *   客户端访问需要认证的接口时，携带 Access Token
 *   如果 Access Token 没有过期，服务端鉴权后返回给客户端需要的数据
 *   如果携带 Access Token 访问需要认证的接口时鉴权失败（例如返回 401 错误），则客户端使用 Refresh Token 向刷新接口申请新的 Access Token
 *   如果 Refresh Token 没有过期，服务端向客户端下发新的 Access Token；如果过期，则重新登录
 *   客户端使用新的 Access Token 访问需要认证的接口
 */
public class JwtUtils {
    /**
     * token 过期时间, 单位: 毫秒.
     *   30 * 24 * 60 * 60 * 1000 表示 30 天
     *   2 * 60 * 1000 表示 2分钟
     */
    // private static final long TOKEN_EXPIRED_TIME = 30 * 24 * 60 * 60 * 1000;

    public static final String JWT_ID = "tokenId";

    /**
     * jwt 加密解密密钥(可自行填写)
     */
    private static final String JWT_SECRET = "CiiC";

    /**
     * 创建JWT
     */
    public static String createJWT(Map<String, Object> claims, int time) {

        Date now = new Date(System.currentTimeMillis());

        // 生成签名密钥（明文密钥经过base64编码后的字符串）
        // SecretKey secretKey = generateKey();
        String secretKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes());
        // 生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        //下面就是在为payload添加各种标准声明和私有声明了
        //  这里其实就是new一个JwtBuilder，设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置 JWT ID：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击
                .setId(JWT_ID)
                // iat: jwt的签发时间
                .setIssuedAt(now)
                // 设置签名使用的签名算法（也就是header那部分，jjwt已经将这部分内容封装好了）和签名使用的秘钥（明文密钥经过base64编码后的字符串）
                .signWith(SignatureAlgorithm.HS256, secretKey);
        if (time >= 0) {
            long expMillis = nowMillis + time * 1000;
            Date exp = new Date(expMillis);
            // 设置过期时间
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * 创建 accessToken
     * @param claims
     * @return
     */
    public static String createAccessToken(Map<String, Object> claims) {
        return createJWT(claims, Constants.ACCESS_TOKEN_TIMEOUT);
    }

    /**
     * 创建 refreshToken
     * @param claims
     * @return
     */
    public static String createRefreshToken(Map<String, Object> claims) {
        return createJWT(claims, Constants.REFRESH_TOKEN_TIMEOUT);
    }

    /**
     * 验证jwt，并返回jwt中的claims信息
     */
    public static Claims verifyJwt(String token) {
        // 验签秘钥（明文密钥经过base64编码后的字符串，作为方法的参数），和生成的签名的秘钥一模一样
        // SecretKey key = generateKey();
        String key = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes());
        Claims claims;

        try {
            claims = Jwts
                // 得到DefaultJwtParser
                .parser()
                // 设置签名的密钥
                .setSigningKey(key)
                .parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
            System.err.println("token已过期或无效！" + e.getMessage());
        }
        return claims;
    }

    /**
     * 由字符串生成加密key
     *
     * @return
    public static SecretKey generateKey() {
        byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
     */
}